using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using JetBrains.Annotations;

namespace Microsoft.EntityFrameworkCore.Dm.Utilities;

[DebuggerStepThrough]
internal static class SharedTypeExtensions
{
	private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string>
	{
		{
			typeof(bool),
			"bool"
		},
		{
			typeof(byte),
			"byte"
		},
		{
			typeof(char),
			"char"
		},
		{
			typeof(decimal),
			"decimal"
		},
		{
			typeof(double),
			"double"
		},
		{
			typeof(float),
			"float"
		},
		{
			typeof(int),
			"int"
		},
		{
			typeof(long),
			"long"
		},
		{
			typeof(object),
			"object"
		},
		{
			typeof(sbyte),
			"sbyte"
		},
		{
			typeof(short),
			"short"
		},
		{
			typeof(string),
			"string"
		},
		{
			typeof(uint),
			"uint"
		},
		{
			typeof(ulong),
			"ulong"
		},
		{
			typeof(ushort),
			"ushort"
		},
		{
			typeof(void),
			"void"
		}
	};

	private static readonly Dictionary<Type, object> _commonTypeDictionary = new Dictionary<Type, object>
	{
		{
			typeof(int),
			0
		},
		{
			typeof(Guid),
			default(Guid)
		},
		{
			typeof(DateTime),
			default(DateTime)
		},
		{
			typeof(DateTimeOffset),
			default(DateTimeOffset)
		},
		{
			typeof(long),
			0L
		},
		{
			typeof(bool),
			false
		},
		{
			typeof(double),
			0.0
		},
		{
			typeof(short),
			(short)0
		},
		{
			typeof(float),
			0f
		},
		{
			typeof(byte),
			(byte)0
		},
		{
			typeof(char),
			'\0'
		},
		{
			typeof(uint),
			0u
		},
		{
			typeof(ushort),
			(ushort)0
		},
		{
			typeof(ulong),
			0uL
		},
		{
			typeof(sbyte),
			(sbyte)0
		}
	};

	private static readonly MethodInfo _generateDefaultValueConstantMethod = typeof(SharedTypeExtensions).GetTypeInfo().GetDeclaredMethod("GenerateDefaultValueConstant");

	public static Type UnwrapNullableType(this Type type)
	{
		return Nullable.GetUnderlyingType(type) ?? type;
	}

	public static bool IsNullableValueType(this Type type)
	{
		if (type.IsGenericType)
		{
			return type.GetGenericTypeDefinition() == typeof(Nullable<>);
		}
		return false;
	}

	public static bool IsNullableType(this Type type)
	{
		if (type.IsValueType)
		{
			return type.IsNullableValueType();
		}
		return true;
	}

	public static bool IsValidEntityType(this Type type)
	{
		return type.IsClass;
	}

	public static bool IsPropertyBagType(this Type type)
	{
		if (type.IsGenericTypeDefinition)
		{
			return false;
		}
		return type.GetGenericTypeImplementations(typeof(IDictionary<, >)).Any((Type t) => t.GetGenericArguments()[0] == typeof(string) && t.GetGenericArguments()[1] == typeof(object));
	}

	public static Type MakeNullable(this Type type, bool nullable = true)
	{
		if (type.IsNullableType() != nullable)
		{
			if (!nullable)
			{
				return type.UnwrapNullableType();
			}
			return typeof(Nullable<>).MakeGenericType(type);
		}
		return type;
	}

	public static bool IsNumeric(this Type type)
	{
		type = type.UnwrapNullableType();
		if (!type.IsInteger() && !(type == typeof(decimal)) && !(type == typeof(float)))
		{
			return type == typeof(double);
		}
		return true;
	}

	public static bool IsInteger(this Type type)
	{
		type = type.UnwrapNullableType();
		if (!(type == typeof(int)) && !(type == typeof(long)) && !(type == typeof(short)) && !(type == typeof(byte)) && !(type == typeof(uint)) && !(type == typeof(ulong)) && !(type == typeof(ushort)) && !(type == typeof(sbyte)))
		{
			return type == typeof(char);
		}
		return true;
	}

	public static bool IsSignedInteger(this Type type)
	{
		if (!(type == typeof(int)) && !(type == typeof(long)) && !(type == typeof(short)))
		{
			return type == typeof(sbyte);
		}
		return true;
	}

	public static bool IsAnonymousType(this Type type)
	{
		if (type.Name.StartsWith("<>", StringComparison.Ordinal) && type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Length != 0)
		{
			return type.Name.Contains("AnonymousType");
		}
		return false;
	}

	public static bool IsTupleType(this Type type)
	{
		if (type == typeof(Tuple))
		{
			return true;
		}
		if (type.IsGenericType)
		{
			Type genericTypeDefinition = type.GetGenericTypeDefinition();
			if (genericTypeDefinition == typeof(Tuple<>) || genericTypeDefinition == typeof(Tuple<, >) || genericTypeDefinition == typeof(Tuple<, , >) || genericTypeDefinition == typeof(Tuple<, , , >) || genericTypeDefinition == typeof(Tuple<, , , , >) || genericTypeDefinition == typeof(Tuple<, , , , , >) || genericTypeDefinition == typeof(Tuple<, , , , , , >) || genericTypeDefinition == typeof(Tuple<, , , , , , , >) || genericTypeDefinition == typeof(Tuple<, , , , , , , >))
			{
				return true;
			}
		}
		return false;
	}

	public static PropertyInfo GetAnyProperty(this Type type, string name)
	{
		string name2 = name;
		List<PropertyInfo> list = (from p in type.GetRuntimeProperties()
			where p.Name == name2
			select p).ToList();
		if (list.Count > 1)
		{
			throw new AmbiguousMatchException();
		}
		return list.SingleOrDefault();
	}

	public static MethodInfo GetRequiredMethod(this Type type, string name, params Type[] parameters)
	{
		MethodInfo method = type.GetTypeInfo().GetMethod(name, parameters);
		if (method == null && parameters.Length == 0)
		{
			method = type.GetMethod(name);
		}
		if (method == null)
		{
			throw new InvalidOperationException();
		}
		return method;
	}

	public static PropertyInfo GetRequiredProperty(this Type type, string name)
	{
		PropertyInfo? property = type.GetTypeInfo().GetProperty(name);
		if (property == null)
		{
			throw new InvalidOperationException();
		}
		return property;
	}

	public static FieldInfo GetRequiredDeclaredField(this Type type, string name)
	{
		FieldInfo? declaredField = type.GetTypeInfo().GetDeclaredField(name);
		if (declaredField == null)
		{
			throw new InvalidOperationException();
		}
		return declaredField;
	}

	public static MethodInfo GetRequiredDeclaredMethod(this Type type, string name)
	{
		MethodInfo? declaredMethod = type.GetTypeInfo().GetDeclaredMethod(name);
		if (declaredMethod == null)
		{
			throw new InvalidOperationException();
		}
		return declaredMethod;
	}

	public static PropertyInfo GetRequiredDeclaredProperty(this Type type, string name)
	{
		PropertyInfo? declaredProperty = type.GetTypeInfo().GetDeclaredProperty(name);
		if (declaredProperty == null)
		{
			throw new InvalidOperationException();
		}
		return declaredProperty;
	}

	public static MethodInfo GetRequiredRuntimeMethod(this Type type, string name, params Type[] parameters)
	{
		MethodInfo? runtimeMethod = type.GetTypeInfo().GetRuntimeMethod(name, parameters);
		if (runtimeMethod == null)
		{
			throw new InvalidOperationException();
		}
		return runtimeMethod;
	}

	public static PropertyInfo GetRequiredRuntimeProperty(this Type type, string name)
	{
		PropertyInfo? runtimeProperty = type.GetTypeInfo().GetRuntimeProperty(name);
		if (runtimeProperty == null)
		{
			throw new InvalidOperationException();
		}
		return runtimeProperty;
	}

	public static bool IsInstantiable(this Type type)
	{
		if (!type.IsAbstract && !type.IsInterface)
		{
			if (type.IsGenericType)
			{
				return !type.IsGenericTypeDefinition;
			}
			return true;
		}
		return false;
	}

	public static Type UnwrapEnumType(this Type type)
	{
		bool flag = type.IsNullableType();
		Type type2 = (flag ? type.UnwrapNullableType() : type);
		if (!type2.IsEnum)
		{
			return type;
		}
		Type underlyingType = Enum.GetUnderlyingType(type2);
		if (!flag)
		{
			return underlyingType;
		}
		return underlyingType.MakeNullable();
	}

	public static Type GetSequenceType(this Type type)
	{
		Type? type2 = type.TryGetSequenceType();
		if (type2 == null)
		{
			throw new ArgumentException();
		}
		return type2;
	}

	public static Type? TryGetSequenceType(this Type type)
	{
		return type.TryGetElementType(typeof(IEnumerable<>)) ?? type.TryGetElementType(typeof(IAsyncEnumerable<>));
	}

	public static Type? TryGetElementType(this Type type, Type interfaceOrBaseType)
	{
		if (type.IsGenericTypeDefinition)
		{
			return null;
		}
		IEnumerable<Type> genericTypeImplementations = type.GetGenericTypeImplementations(interfaceOrBaseType);
		Type type2 = null;
		foreach (Type item in genericTypeImplementations)
		{
			if (type2 == null)
			{
				type2 = item;
				continue;
			}
			type2 = null;
			break;
		}
		return type2?.GenericTypeArguments.FirstOrDefault();
	}

	public static bool IsCompatibleWith(this Type propertyType, Type fieldType)
	{
		if (propertyType.IsAssignableFrom(fieldType) || fieldType.IsAssignableFrom(propertyType))
		{
			return true;
		}
		Type type = propertyType.TryGetSequenceType();
		Type type2 = fieldType.TryGetSequenceType();
		if (type != null && type2 != null)
		{
			return type.IsCompatibleWith(type2);
		}
		return false;
	}

	public static IEnumerable<Type> GetGenericTypeImplementations(this Type type, Type interfaceOrBaseType)
	{
		TypeInfo typeInfo = type.GetTypeInfo();
		if (typeInfo.IsGenericTypeDefinition)
		{
			yield break;
		}
		IEnumerable<Type> enumerable;
		if (!interfaceOrBaseType.GetTypeInfo().IsInterface)
		{
			enumerable = type.GetBaseTypes();
		}
		else
		{
			IEnumerable<Type> implementedInterfaces = typeInfo.ImplementedInterfaces;
			enumerable = implementedInterfaces;
		}
		IEnumerable<Type> enumerable2 = enumerable;
		foreach (Type item in enumerable2)
		{
			if (item.IsGenericType && item.GetGenericTypeDefinition() == interfaceOrBaseType)
			{
				yield return item;
			}
		}
		if (type.IsGenericType && type.GetGenericTypeDefinition() == interfaceOrBaseType)
		{
			yield return type;
		}
	}

	public static IEnumerable<Type> GetBaseTypes(this Type type)
	{
		type = type.BaseType;
		while (type != null)
		{
			yield return type;
			type = type.BaseType;
		}
	}

	public static IEnumerable<Type> GetTypesInHierarchy(this Type type)
	{
		while (type != null)
		{
			yield return type;
			type = type.BaseType;
		}
	}

	public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] types)
	{
		if (types == null)
		{
			types = Array.Empty<Type>();
		}
		return type.GetTypeInfo().DeclaredConstructors.SingleOrDefault((ConstructorInfo c) => !c.IsStatic && (from p in c.GetParameters()
			select p.ParameterType).SequenceEqual(types));
	}

	public static IEnumerable<PropertyInfo> GetPropertiesInHierarchy(this Type type, string name)
	{
		do
		{
			TypeInfo typeInfo = type.GetTypeInfo();
			foreach (PropertyInfo declaredProperty in typeInfo.DeclaredProperties)
			{
				if (declaredProperty.Name.Equals(name, StringComparison.Ordinal) && !(declaredProperty.GetMethod ?? declaredProperty.SetMethod).IsStatic)
				{
					yield return declaredProperty;
				}
			}
			type = typeInfo.BaseType;
		}
		while (type != null);
	}

	public static IEnumerable<MemberInfo> GetMembersInHierarchy(this Type type)
	{
		do
		{
			foreach (PropertyInfo item in from pi in type.GetRuntimeProperties()
				where !(pi.GetMethod ?? pi.SetMethod).IsStatic
				select pi)
			{
				yield return item;
			}
			foreach (FieldInfo item2 in from f in type.GetRuntimeFields()
				where !f.IsStatic
				select f)
			{
				yield return item2;
			}
			type = type.BaseType;
		}
		while (type != null);
	}

	public static IEnumerable<MemberInfo> GetMembersInHierarchy(this Type type, string name)
	{
		return from m in type.GetMembersInHierarchy()
			where m.Name == name
			select m;
	}

	public static object GetDefaultValue(this Type type)
	{
		if (!type.IsValueType)
		{
			return null;
		}
		if (!_commonTypeDictionary.TryGetValue(type, out var value))
		{
			return Activator.CreateInstance(type);
		}
		return value;
	}

	public static IEnumerable<TypeInfo> GetConstructibleTypes(this Assembly assembly)
	{
		return from t in assembly.GetLoadableDefinedTypes()
			where !t.IsAbstract && !t.IsGenericTypeDefinition
			select t;
	}

	public static IEnumerable<TypeInfo> GetLoadableDefinedTypes(this Assembly assembly)
	{
		try
		{
			return assembly.DefinedTypes;
		}
		catch (ReflectionTypeLoadException ex)
		{
			return ex.Types.Where((Type t) => t != null).Select(IntrospectionExtensions.GetTypeInfo);
		}
	}

	public static bool IsQueryableType(this Type type)
	{
		if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IQueryable<>))
		{
			return true;
		}
		return type.GetInterfaces().Any((Type i) => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueryable<>));
	}

	public static string DisplayName([NotNull] this Type type, bool fullName = true)
	{
		StringBuilder stringBuilder = new StringBuilder();
		ProcessType(stringBuilder, type, fullName);
		return stringBuilder.ToString();
	}

	private static void ProcessType(StringBuilder builder, Type type, bool fullName)
	{
		string value;
		if (type.IsGenericType)
		{
			Type[] genericArguments = type.GetGenericArguments();
			ProcessGenericType(builder, type, genericArguments, genericArguments.Length, fullName);
		}
		else if (type.IsArray)
		{
			ProcessArrayType(builder, type, fullName);
		}
		else if (_builtInTypeNames.TryGetValue(type, out value))
		{
			builder.Append(value);
		}
		else if (!type.IsGenericParameter)
		{
			builder.Append(fullName ? type.FullName : type.Name);
		}
	}

	private static void ProcessArrayType(StringBuilder builder, Type type, bool fullName)
	{
		Type type2 = type;
		while (type2.IsArray)
		{
			type2 = type2.GetElementType();
		}
		ProcessType(builder, type2, fullName);
		while (type.IsArray)
		{
			builder.Append('[');
			builder.Append(',', type.GetArrayRank() - 1);
			builder.Append(']');
			type = type.GetElementType();
		}
	}

	private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, bool fullName)
	{
		int num = (type.IsNested ? type.DeclaringType.GetGenericArguments().Length : 0);
		if (fullName)
		{
			if (type.IsNested)
			{
				ProcessGenericType(builder, type.DeclaringType, genericArguments, num, fullName);
				builder.Append('+');
			}
			else
			{
				builder.Append(type.Namespace);
				builder.Append('.');
			}
		}
		int num2 = type.Name.IndexOf('`');
		if (num2 <= 0)
		{
			builder.Append(type.Name);
			return;
		}
		builder.Append(type.Name, 0, num2);
		builder.Append('<');
		for (int i = num; i < length; i++)
		{
			ProcessType(builder, genericArguments[i], fullName);
			if (i + 1 != length)
			{
				builder.Append(',');
				if (!genericArguments[i + 1].IsGenericParameter)
				{
					builder.Append(' ');
				}
			}
		}
		builder.Append('>');
	}

	public static IEnumerable<string> GetNamespaces([NotNull] this Type type)
	{
		if (_builtInTypeNames.ContainsKey(type))
		{
			yield break;
		}
		yield return type.Namespace;
		if (!type.IsGenericType)
		{
			yield break;
		}
		Type[] genericTypeArguments = type.GenericTypeArguments;
		foreach (Type type2 in genericTypeArguments)
		{
			foreach (string @namespace in type2.GetNamespaces())
			{
				yield return @namespace;
			}
		}
	}

	public static ConstantExpression GetDefaultValueConstant(this Type type)
	{
		return (ConstantExpression)_generateDefaultValueConstantMethod.MakeGenericMethod(type).Invoke(null, Array.Empty<object>());
	}

	private static ConstantExpression GenerateDefaultValueConstant<TDefault>()
	{
		return Expression.Constant(default(TDefault), typeof(TDefault));
	}
}
